package per.cy.common.util;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

import javax.crypto.*;
import java.io.*;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * @author cy
 * date 2024-08-16
 */
public class AESUtil {

    private static final Log log = LogFactory.get();

    // 定义缓冲区大小为16KB
    private static final int BUFFER_SIZE = 16384;

    // 指定AES加密算法的名称
    private static final String AES_ALGORITHM = "AES";

    // 指定AES加密转换模式，包括加密算法、工作模式和填充方式
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";

    /**
     * 生成一个256位的AES密钥
     *
     * @return 返回生成的SecretKey对象
     */
    public static SecretKey generateAES256Key() throws NoSuchAlgorithmException {
        KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGORITHM);
        // 初始化为256位
        keyGen.init(256);
        return keyGen.generateKey();
    }

    /**
     * 将AES密钥转换为Base64编码的字符串
     *
     * @return 返回Base64编码的字符串
     */
    public static String keyToString() throws NoSuchAlgorithmException {
        // 使用Base64编码器将AES密钥转换为字符串
        return Base64.getEncoder().encodeToString(generateAES256Key().getEncoded());
    }

    /**
     * 将SecretKey对象转换为Base64编码的字符串
     * <p>
     * 该方法使用Java自带的Base64编码器，对SecretKey对象进行序列化后，返回其Base64编码的字符串表示
     * 这在需要通过字符串形式保存或传输密钥时非常有用，例如在配置文件中指定加密密钥
     *
     * @param secretKey 要转换的SecretKey对象
     * @return Base64编码的密钥字符串
     */
    public static String keyToString(SecretKey secretKey) {
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }


    /**
     * 将字符串转换为SecretKey对象
     * 该方法主要用于将通过Base64编码的密钥字符串转换为可用于加密/解密操作的密钥对象
     *
     * @param keyString 密钥字符串，应为Base64编码格式
     * @return 返回一个实现了SecretKey接口的对象，该对象包含了密钥信息
     * @throws InvalidKeyException 如果密钥字符串无效
     */
    public static SecretKey stringToKey(String keyString) throws InvalidKeyException {
        try {
            byte[] decodedKey = Base64.getDecoder().decode(keyString);
            return new Base64SecretKey(AES_ALGORITHM, decodedKey);
        } catch (IllegalArgumentException e) {
            throw new InvalidKeyException("Invalid Base64 encoding: " + e.getMessage(), e);
        }
    }

    /**
     * 使用AES加密算法加密文件
     *
     * @param inputPath  输入文件的路径
     * @param outputPath 输出文件的路径
     * @param secretKey  用于加密的密钥
     */
    public static void encrypt(String inputPath, String outputPath, SecretKey secretKey) {
        // 创建输入文件对象
        File inFile = new File(inputPath);
        // 创建输出文件对象
        File outFile = new File(outputPath);
        try (InputStream in = new BufferedInputStream(Files.newInputStream(inFile.toPath()));
             OutputStream out = new BufferedOutputStream(Files.newOutputStream(outFile.toPath()))
        ) {

            // 初始化Cipher对象
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            // 设置加密模式和密钥
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            // 调用方法将输入流中的数据进行加密并写入输出流
            cipherUpdateBytesContent(in, out, cipher);

        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
            // 记录加密算法或密钥相关的问题
            log.error("Encryption algorithm or key issue: " + e.getMessage(), e);
            throw new RuntimeException("Error initializing the encryption process: " + e.getMessage(), e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            // 记录数据加密过程中的问题
            log.error("Data encryption issue: " + e.getMessage(), e);
            throw new RuntimeException("Error during data encryption: " + e.getMessage(), e);
        } catch (IOException e) {
            // 记录文件I/O操作中的问题
            log.error("File I/O error: " + e.getMessage(), e);
            throw new RuntimeException("Error during file I/O operations: " + e.getMessage(), e);
        }

    }


    /**
     * 对输入流内容进行加密，并将加密内容写入输出流
     *
     * @param in     输入流，用于读取需要加密的数据
     * @param out    输出流，用于写入加密后的数据
     * @param cipher 加密算法实例
     * @throws IOException               如果读取或写入过程中发生I/O错误
     * @throws IllegalBlockSizeException 如果加密过程中发生非法块大小错误
     * @throws BadPaddingException       如果加密过程中发生填充错误
     */
    private static void cipherUpdateBytesContent(InputStream in, OutputStream out, Cipher cipher) throws IOException, IllegalBlockSizeException, BadPaddingException {
        // 创建字节数组用于读取输入文件
        byte[] inputBytes = new byte[BUFFER_SIZE];
        // 用于保存每次读取的字节数
        int len;

        // 循环读取文件内容并加密
        while ((len = in.read(inputBytes)) != -1) {
            // 加密数据
            byte[] outputBytes = cipher.update(inputBytes, 0, len);
            // 如果有输出，则写入到输出流
            if (outputBytes != null) {
                out.write(outputBytes);
            }
        }

        // 加密剩余数据
        byte[] outputBytes = cipher.doFinal();
        // 如果有输出，则写入到输出流
        if (outputBytes != null) {
            out.write(outputBytes);
        }
    }


    /**
     * 使用AES算法对文件进行解密
     *
     * @param inputPath  待解密文件的路径
     * @param outputPath 解密后文件的保存路径
     * @param secretKey  用于解密的密钥
     * @throws RuntimeException 如果解密过程中出现错误，将抛出运行时异常
     */
    public static void decrypt(String inputPath, String outputPath, SecretKey secretKey) {
        // 创建输入文件对象
        File inFile = new File(inputPath);
        // 创建输出文件对象
        File outFile = new File(outputPath);
        try (
                // 创建输入流，用于读取待解密文件
                InputStream in = new BufferedInputStream(Files.newInputStream(inFile.toPath()));
                // 创建输出流，用于保存解密后的文件
                OutputStream out = new BufferedOutputStream(Files.newOutputStream(outFile.toPath()))) {

            // 创建AES密码实例
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            // 初始化密码实例为解密模式
            cipher.init(Cipher.DECRYPT_MODE, secretKey);

            cipherUpdateBytesContent(in, out, cipher);

        } catch (NoSuchAlgorithmException e) {
            log.error("No such algorithm: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("No such algorithm: " + e.getMessage(), e);
        } catch (InvalidKeyException e) {
            log.error("Invalid key: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("Invalid key: " + e.getMessage(), e);
        } catch (NoSuchPaddingException e) {
            log.error("No such padding: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("No such padding: " + e.getMessage(), e);
        } catch (IOException e) {
            log.error("IO error: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("IO error: " + e.getMessage(), e);
        } catch (BadPaddingException e) {
            log.error("Bad padding: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("Bad padding: " + e.getMessage(), e);
        } catch (IllegalBlockSizeException e) {
            log.error("Illegal block size: " + e.getMessage() + ". Path: " + inputPath);
            throw new RuntimeException("Illegal block size: " + e.getMessage(), e);
        }
    }


    /**
     * 实现SecretKey接口的具体类
     */
    static class Base64SecretKey implements SecretKey {

        private final String algorithm;
        private final byte[] encoded;

        /**
         * 构造函数
         *
         * @param algorithm 算法名称
         * @param encoded   密钥的字节表示
         */
        public Base64SecretKey(String algorithm, byte[] encoded) {
            this.algorithm = algorithm;
            this.encoded = encoded;
        }

        @Override
        public String getAlgorithm() {
            return algorithm;
        }

        @Override
        public String getFormat() {
            return "RAW";
        }

        @Override
        public byte[] getEncoded() {
            return encoded;
        }
    }
}

